<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>

<body>
  <script>
    // 视图更新后的操作
    function cb(val) {
      console.log('视图更新了', val);
    }
    // 对对象的某个属性设置 setter 与 getter 函数
    // obj:需要绑定的对象
    // key:obj的某一个属性
    // val：具体值
    function defineReactive(obj, key, val) {
      /* 一个Dep类对象 */
      const dep = new Dep();
      Object.defineProperty(obj, key, {
        enumerable: true,       /* 属性可枚举 */
        configurable: true,     /* 属性可被修改或删除 */
        get: function reactiveGetter() {
          /* 将Dep.target（即当前的Watcher对象存入dep的subs中） */
          console.log('get执行了');
          dep.addSub(Dep.target);
          return val;
        },
        set: function reactiveSetter(newVal) {
          if (newVal === val) return;
          /* 在set的时候触发dep的notify来通知所有的Watcher对象更新视图 */
          console.log('set执行了');
          dep.notify();
          // val = newVal;
          // cb(newVal);
        }
      })
    }
    // 遍历对象的每个属性,设置setter getter方法
    // value:需要响应化的对象
    function observer(value) {
      if (!value || (typeof value !== 'object')) {
        return;
      }

      Object.keys(value).forEach((key) => {
        defineReactive(value, key, value[key]);
      });
    }

    class Dep {
      constructor() {
        /* 用来存放Watcher对象的数组 */
        this.subs = [];
      }

      /* 在subs中添加一个Watcher对象 */
      addSub(sub) {
        this.subs.push(sub);
      }

      /* 通知所有Watcher对象更新视图 */
      notify() {
        this.subs.forEach((sub) => {
          sub.update();
        })
      }
    }
    class Watcher {
      constructor() {
        /* 在new一个Watcher对象时将该对象赋值给Dep.target，在get中会用到 */
        Dep.target = this;
      }

      /* 更新视图的方法 */
      update() {
        console.log("视图更新啦～");
      }
    }

    // 对vuedata 中的data进行setter和getter
    class Vue {
      /* Vue构造类 */
      constructor(options) {
        this._data = options.data;
        observer(this._data);
        /* 新建一个Watcher观察者对象，这时候Dep.target会指向这个Watcher对象 */
        new Watcher();
        /* 在这里模拟render的过程，为了触发test属性的get函数 */
        console.log('render~', this._data.test);
      }
    }
    let o = new Vue({
      data: {
        test: "I am test."
      }
    });
    o._data.test = "hello,world";  /* 视图更新啦～ */

  </script>
</body>

</html>